#include "../stdafx.h"
#include "UCN2IC10Implementation.h"

#include "UCN2IC10Windows.h"
#include "../UIniFile/UIniFile.h"
#include "../Controls/UMemo.h"
#include "../UGLFont.h"
#include "../UColor.h"
#include "../ULeafWindow.h"
#include "../UContainerWindow.h"
#include "../UNonSpaceFillingContainerWindow.h"

#include <string>
#include <iostream>
#include <sstream>

#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>

using namespace std;

static int myport;

#define SERVER_PORT 0x5555
#define BROADCAST_PORT 0xc001
#define SETTINGS_FILE "CoolGame.ini"

#include "NetworkUtil.h"


void CN2IC10Implementation::SetState(NetState newstate)
{
	netstate = newstate;
	ticks = 0;
// printf("Setting state: %i\n",netstate);
};

// gets called 10 times per second
void CN2IC10Implementation::Ticker()
{
	++ticks;
	//printf("ticker:%i\n",ticks);

	// check if we have anything to do in our current state
	switch (netstate) {
		case NET_STATE_NONE:
			Init();
			return;
		case NET_STATE_INITIALISED: {
			TIniFile inifile(SETTINGS_FILE);
			ChangeUsername(inifile.getValue("network", "username"));
		};break;
		case NET_STATE_USERNAME_REQUESTED:
			if (ticks > 100) // try again if response takes too long
				RequestUsername();
			break;
		case NET_STATE_USERNAME_CONFIRMED: {
			// auto-login (from ini file)
			TIniFile inifile("cn2ic10.ini");
			// get password
			password = inifile.getValue("users", username);
			if (password == "") {
				username += "-dup";
				RequestUsername();
				break;
			}
			password.erase(0, 1);
			password.erase(password.size() - 1, 1);
			// try to login
			RequestLogin();
		};break;
		case NET_STATE_LOGIN_REQUESTED:
			if (ticks > 100) // try again if response takes too long
				RequestLogin();
			break;
		case NET_STATE_LOGIN_CONFIRFMED:
			// if 60 seconds elapsed, login again for new userlist
			if (ticks > 600)
				RequestLogin();
			break;
	}

	// poll for new udp packets
	while ((SDLNet_UDP_Recv(udpSock, udpPacket) > 0) || (bcSock && SDLNet_UDP_Recv(bcSock, udpPacket) > 0)) {
		//cout << "packet: " << (int)udpPacket->data[0] << endl;
		switch (udpPacket->data[0]) { // packet type
			case 1: { // announced
				printf("received one\n");
				string cname;
				parsePacket1(udpPacket, &cname);
				userinfo[cname].addr = udpPacket->address;
				if (!userinfo[cname].online) {
					onlineUsers.Lines.push_back(cname);
					//offlineUsers.Lines.erase
					userinfo[cname].online = true;
				}
			};break;
			case 2: { // username request reply
				string uname;
				string pwd;
				parsePacket2(udpPacket, &uname, &pwd);
				if (uname == username) {
					// store password
					password = pwd;
					servaddr = udpPacket->address;
					servaddr.port = SDL_SwapBE16(SERVER_PORT);
					SetState(NET_STATE_USERNAME_CONFIRMED);
				}
				//cout << "U:" << uname.c_str() << " P:" << pwd.c_str() << endl;
				if (password != "") {
					// save password to ini file
					TIniFile inifile("cn2ic10.ini");
//     inifile.addValue("users", username, "\"" + password + "\""); TO-DO
					inifile.Save("cn2ic10.ini");
				}
			};break;
			case 4: { // user list
				editUserName.SetText(username);
				vector<User> users;
				parsePacket4(udpPacket, &users);
				// move current online users to offline
				offlineUsers.Lines.insert(offlineUsers.Lines.end(), onlineUsers.Lines.begin(), onlineUsers.Lines.end());
				onlineUsers.Lines.clear();
				// add list just received to online users;
				// remove duplicates from offlineusers
				for (uint i = 0; i < users.size(); ++i) {
					string cname = users[i].name;
					onlineUsers.Lines.push_back(cname);


					uint32 ip = users[i].ip;
					stringstream ss;
					ss << "<" << ((ip >> 0)&0xff) << "." << ((ip >> 8)&0xff) << "." << ((ip >> 16)&0xff) << "." << ((ip >> 24)&0xff) << ">";

					map<string, UserInfo>::iterator iter1;
					iter1 = userinfo.find(ss.str());
					if (iter1 != userinfo.end()) {
						UserInfo tu = iter1->second;
						userinfo.erase(iter1);
						userinfo[cname] = tu;
						if (userinfo[cname].window) {
							userinfo[cname].window->SetUser(cname);
						}
					}

					userinfo[cname].online = true;
					userinfo[cname].addr.host = users[i].ip;
//     userinfo[cname].addr.port = SDL_SwapBE16(CLIENT_PORT); TO-DO

					vector<string>::iterator iter2;
					iter2 = find(offlineUsers.Lines.begin(), offlineUsers.Lines.end(), cname);
					if (iter2 != offlineUsers.Lines.end())
						offlineUsers.Lines.erase(iter2);
				}

				{
					vector<string>::iterator iter2;
					for (iter2 = offlineUsers.Lines.begin(); iter2 != offlineUsers.Lines.end(); ++iter2)
						userinfo[*iter2].online = false;
				}

				SetState(NET_STATE_LOGIN_CONFIRFMED);
			};break;
			case 5: { // direct message
				uint8 type;
				uint32 size, port;
				parsePacket5(udpPacket, &type, &size, &port);
				InMessageRef inmsg(new InMessage());
				IPaddress addr;
				addr.host = udpPacket->address.host;
				addr.port = SDL_SwapBE16(port);
				inmsg->sock = SDLNet_TCP_Open(&addr);
				inmsg->size = size;
				inmsg->type = type;
				inmsg->inpos = 0;
				inmsg->srcuser = FindUserByIp(udpPacket->address);
				inqueue.push_back(inmsg);
			};break;
			case 7: { // indirect message
				uint8 type;
				uint32 size, port, stamp;
				string fromuser;
				parsePacket7(udpPacket, &type, &size, &port, &fromuser, &stamp);
				InMessageRef inmsg(new InMessage());
				IPaddress addr;
				addr.host = udpPacket->address.host;
				addr.port = SDL_SwapBE16(port);
				inmsg->sock = SDLNet_TCP_Open(&addr);
				inmsg->size = size;
				inmsg->type = type;
				inmsg->inpos = 0;
				inmsg->srcuser = fromuser;
				inqueue.push_back(inmsg);
			};break;
			default: ;
				//ignore
		}
	}

	// check outgoing message queue
	BOOST_FOREACH(OutMessageRef outmsg, outqueue) {
		if (outmsg) {
			if (!outmsg->connected) {
				TCPsocket newsock = SDLNet_TCP_Accept(outmsg->sock);
				if (newsock != NULL) {
					SDLNet_TCP_Close(outmsg->sock);
					outmsg->sock = newsock;
					outmsg->connected = true;
				}
			}
			if (outmsg->connected) {
				int numsend = outmsg->data.size() - outmsg->outpos;
				if (numsend > 0) {
					outmsg->outpos +=
						SDLNet_TCP_Send(outmsg->sock, &outmsg->data[outmsg->outpos], numsend);
				} else {
					outmsg.reset(); // clean up
				}
			}
		}
	}

	// check incoming message queue
	BOOST_FOREACH(InMessageRef inmsg, inqueue) {
		if (inmsg) {
			inmsg->data.resize(inmsg->size);
			if (inmsg->sock) {
				int rlen = SDLNet_TCP_Recv(inmsg->sock, &inmsg->data[inmsg->inpos], inmsg->size - inmsg->inpos);
				//cout << "r:" << rlen << endl;
				if (rlen > 0) {
					inmsg->inpos += rlen;
					//cout << "p:" << inmsg->inpos << " s:" << inmsg->size << endl;
					if (inmsg->inpos == inmsg->size) {
						HandleMessage(inmsg->type, &inmsg->data[0], inmsg->size, inmsg->srcuser);
						SDLNet_TCP_Close(inmsg->sock);
						inmsg->sock = NULL;
					}
				} else {
					SDLNet_TCP_Close(inmsg->sock);
					inmsg->sock = NULL;
				}
			} else {
				inmsg.reset(); // clean up
			}
		}
	}
}

void CN2IC10Implementation::Init()
{
	if (SDLNet_Init() == -1) {
		printf("SDLNet_Init: %s\n", SDLNet_GetError());
		return;
	}

	udpPacket = SDLNet_AllocPacket(1400);
	if (!udpPacket) {
		printf("SDLNet_AllocPacket: %s\n", SDLNet_GetError());
		return;
	}

	udpSock = NULL;

	TIniFile inifile(SETTINGS_FILE);
	string port = inifile.getValue("network", "udpport");
	if (port != "")
		myport = atoi(port.c_str());
	else
		myport = (rand() | 0x8000) & 0xffff;

	udpSock = SDLNet_UDP_Open(myport);

	int tries = 100;
	while (!udpSock && --tries > 0) {
		myport = (rand() | 0x8000) & 0xffff;
		udpSock = SDLNet_UDP_Open(myport);
	}

	if (!udpSock) {
		printf("SDLNet_UDP_Open: %s\n", SDLNet_GetError());
		return;
	}

	bcSock = SDLNet_UDP_Open(BROADCAST_PORT);
	if (!bcSock) {
		printf("SDLNet_UDP_Open: %s\n", SDLNet_GetError());
		printf("failed to listen on broadcast port\n");
	}

	SetState(NET_STATE_INITIALISED);
}

void CN2IC10Implementation::RequestUsername()
{
	makePacket1(udpPacket, username);
	makeBroadcastPacket(udpPacket, BROADCAST_PORT);
	int numsend = SDLNet_UDP_Send(udpSock, -1, udpPacket);
	if (!numsend)
		printf("SDLNet_UDP_Send: %s\n", SDLNet_GetError());
	else
		SetState(NET_STATE_USERNAME_REQUESTED);
}

void CN2IC10Implementation::ChangeUsername(std::string name)
{
	username = name;
	if (username == "") {
		char* envuser = NULL;
		if (!envuser) envuser = getenv("USER");     // unix
		if (!envuser) envuser = getenv("USERNAME"); // win (nt+?)
		if (envuser) username = envuser;
	}

	if (username == "") {
		editUserName.SetText("<Pick User Name>");
	} else {
		editUserName.SetText(username);

		TIniFile inifile(SETTINGS_FILE);
		inifile.setValue("network", "username", username);
		inifile.Save();

		RequestUsername();
	}
}


void CN2IC10Implementation::RequestLogin()
{
	makePacket3(udpPacket, username, password);
	udpPacket->address = servaddr;
	int numsend = SDLNet_UDP_Send(udpSock, -1, udpPacket);
	if (!numsend)
		printf("SDLNet_UDP_Send: %s\n", SDLNet_GetError());
	else
		SetState(NET_STATE_LOGIN_REQUESTED);
}

CN2IC10Implementation::CN2IC10Implementation(TCN2IC10MainWindow* main)
		: onlineUsers(TRectangle(), TGLFontRef(), RGBAColor(0xff0000), RGBAColor(0xa0a0a0))
		, offlineUsers(TRectangle(), TGLFontRef(), RGBAColor(0x7f0000), RGBAColor(0xa0a0a0))
		, buttonChangeUserName("<Set User Name>", TGLFontRef(), TRectangle())
		, buttonManualUserAdd("<Add offline User>", TGLFontRef(), TRectangle())
		, editUserName(TRectangle())
		, editUserAdd(TRectangle())
		, mainWindow(main)
{
	SetState(NET_STATE_NONE);

	onlineUsers.onClick = boost::bind(&CN2IC10Implementation::clickOnlineUser, this, _1);
	offlineUsers.onClick = boost::bind(&CN2IC10Implementation::clickOfflineUser, this, _1);

	buttonChangeUserName.onClick = editUserName.onEnter = boost::bind(
		&CN2IC10Implementation::ChangeUsername, this,
		boost::bind(
			&TEdit::GetText, &editUserName
		)
	);

	buttonManualUserAdd.onClick = editUserAdd.onEnter = boost::bind(
		&CN2IC10Implementation::AddOfflineUser, this,
		boost::bind(
			&TEdit::GetText, &editUserAdd
		)
	);

	editUserAdd.clearOnEnter = true;
}

void CN2IC10Implementation::clickOfflineUser(int i)
{
	if (0 <= i && (uint)i < offlineUsers.Lines.size()) {
		OpenChatWindow(offlineUsers.Lines[i]);
	}
}

void CN2IC10Implementation::clickOnlineUser(int i)
{
	if (0 <= i && (uint)i < onlineUsers.Lines.size()) {
		OpenChatWindow(onlineUsers.Lines[i]);
	}
}

void CN2IC10Implementation::OpenChatWindow(string user)
{
	if (userinfo[user].window == NULL) {
		userinfo[user].window = mainWindow->newChatWindow(user);
	} else {
		// focus window
		userinfo[user].window->Parent->Parent->RemoveChild(userinfo[user].window->Parent);
		mainWindow->Parent->GetRoot()->AddChild(userinfo[user].window->Parent);
	}
}

void CN2IC10Implementation::SendTextMessage(string user, string message)
{
	OutMessageRef outmsg(new OutMessage());
	outmsg->destuser = user;
	outmsg->data = vector<uint8>(message.begin(), message.end());
	outqueue.push_back(outmsg);

	userinfo[user].window->textmemo->Lines.push_back(username + ": " + message);

	if (userinfo[user].online) {
		makePacket5(udpPacket, 0, message.size(), outmsg->port);
		udpPacket->address = userinfo[user].addr;
		SDLNet_UDP_Send(udpSock, -1, udpPacket);
	} else {
		makePacket6(udpPacket, 0, message.size(), outmsg->port, user);
		udpPacket->address = servaddr;
		SDLNet_UDP_Send(udpSock, -1, udpPacket);
	}
}

void CN2IC10Implementation::SendFileMessage(string user, string filename)
{
	ifstream infile;
	infile.open(filename.c_str(), ios_base::in | ios_base::binary);
	if (!infile.good()) {
		userinfo[user].window->textmemo->Lines.push_back(string("<ERROR: can't open file '") + filename + "'>");
		return;
	}

	OutMessageRef outmsg(new OutMessage());
	outmsg->destuser = user;

	while (!infile.eof()) {
		char buf[1024];
		int num;
		infile.read(buf, 1024);
		num = infile.gcount();
		outmsg->data.insert(outmsg->data.end(), buf, buf + num);
	}

	vector<string> tmp;
	boost::split(tmp, filename, boost::is_any_of("."));
	string ext = tmp.back();
	boost::to_lower(ext);

	int8 type = -1;

	if (ext == "txt" ) type = 0;
	if (ext == "ini" ) type = 0;

	if (ext == "htm" ) type = 1;
	if (ext == "html") type = 1;

	if (ext == "mpg" ) type = 2;
	if (ext == "mpeg") type = 2;
	if (ext == "avi" ) type = 2;

	if (ext == "mp3" ) type = 3;

	if (ext == "jpg" ) type = 4;
	if (ext == "jpeg") type = 4;

	if (ext == "gif" ) type = 5;

	if (ext == "bmp" ) type = 6;

	if (ext == "exe" ) type = 7;

	if (type == -1) {
		//userinfo[user].window->textmemo->Lines.push_back(string("<TRANSFER>: unknown extension '") + ext + "'>");
		type = 8;
		//return;
	}

	outqueue.push_back(outmsg);

	if (userinfo[user].online) {
		makePacket5(udpPacket, type, outmsg->data.size(), outmsg->port);
		udpPacket->address = userinfo[user].addr;
		SDLNet_UDP_Send(udpSock, -1, udpPacket);
		userinfo[user].window->textmemo->Lines.push_back(string("<TRANSFER>: sent file '") + filename + "' direct>");
	} else {
		makePacket6(udpPacket, type, outmsg->data.size(), outmsg->port, user);
		udpPacket->address = servaddr;
		SDLNet_UDP_Send(udpSock, -1, udpPacket);
		userinfo[user].window->textmemo->Lines.push_back(string("<TRANSFER>: sent file '") + filename + "' indirect>");
	}
}

void CN2IC10Implementation::HandleMessage(uint8 type, uint8* data, uint32 size, std::string fromuser)
{
	static int recfiles = 0;
	++recfiles;
	OpenChatWindow(fromuser);
	if (type == 0) {
		userinfo[fromuser].window->textmemo->Lines.push_back(fromuser + ": " + string(data, data + size));
		return;
	}
	string fname;
	stringstream ss;
	ss << recfiles;
	ss >> fname;
	while (fname.size() < 3) fname = string("0") + fname;
	fname = string("received_") + fname + ".";
	        if (type == 1) fname = fname + "htm";
	else if (type == 2) fname = fname + "mpg";
	else if (type == 3) fname = fname + "mp3";
	else if (type == 4) fname = fname + "jpg";
	else if (type == 5) fname = fname + "gif";
	else if (type == 6) fname = fname + "bmp";
	else if (type == 7) fname = fname + "exe";
	else                fname = fname + "dat";

	ofstream outfile;
	outfile.open(fname.c_str(), ios_base::out | ios_base::binary);
	if (!outfile.good()) {
		userinfo[fromuser].window->textmemo->Lines.push_back(string("<ERROR: can't open file '") + fname + "'>");
		return;
	}

	outfile.write((char*)data, size);

	userinfo[fromuser].window->textmemo->Lines.push_back(string("<TRANSFER>: received file '") + fname + "'>");
}

string CN2IC10Implementation::FindUserByIp(IPaddress ip)
{
	typedef std::pair<string, UserInfo> userpair;
	BOOST_FOREACH(userpair p, userinfo) {
		if (p.second.addr.host == ip.host && p.second.addr.port == ip.port)
			return p.first;
	}
	stringstream ss;
	ss << "<" << ((ip.host >> 0)&0xff) << "." << ((ip.host >> 8)&0xff) << "." << ((ip.host >> 16)&0xff) << "." << ((ip.host >> 24)&0xff) << ">";

	userinfo[ss.str()].online = true;
	userinfo[ss.str()].addr = ip;

	return ss.str();
}

void CN2IC10Implementation::AddOfflineUser(std::string user)
{
	userinfo[user].online = false;

	vector<string>::iterator iter;
	iter = find(offlineUsers.Lines.begin(), offlineUsers.Lines.end(), user);
	if (iter != offlineUsers.Lines.end())
		return;

	iter = find(onlineUsers.Lines.begin(), onlineUsers.Lines.end(), user);
	if (iter != onlineUsers.Lines.end())
		onlineUsers.Lines.erase(iter);

	offlineUsers.Lines.push_back(user);
}

OutMessage::OutMessage()
{
	static uint16 portnum = 13370;
	port = ++portnum;
	IPaddress ip;
	ip.host = INADDR_ANY;
	ip.port = SDL_SwapBE16(portnum);
	sock = SDLNet_TCP_Open(&ip);
	connected = false;
	outpos = 0;
}

OutMessage::~OutMessage()
{
	SDLNet_TCP_Close(sock);
}
